Built-in Modules > SmartPacker Class
SmartPacker Class
Beta: SmartPacker is currently in beta. If you notice any errors, unexpected results, or performance issues, please share feedback so we can improve it.
Overview
The SmartPacker class is a packaging utility designed to select shipping boxes for a given set of items. It uses a combination of 3D bin packing for rigid items and heuristic-based packing for flexible, soft, and nestable items to generate realistic parcel groupings while enforcing box dimension and weight constraints.
Class Signature
class SmartPacker
Constructor
new SmartPacker(options)
Arguments
options(Object): Configuration object for the packer.boxes(Array, Required): Array of available box definitions.fillPercentages(Object, Optional): Overrides default space utilization per shipping type.compressionRatios(Object, Optional): Overrides compression ratios for soft items.rigidVoidEfficiency(Number, Optional): Efficiency factor for using remaining void space in rigid packing.
Notes
- All boxes must define a valid
maxWeight. - Item and box dimensions must use the same unit system.
Boxes Input
Each box must follow the structure below:
{
name,
length,
width,
height,
weight,
maxWeight
}
name(String): Identifier returned in output parcels.length,width,height(Number): Inner box dimensions.weight(Number): Empty box weight.maxWeight(Number): Maximum allowed total weight (box + contents).
All numeric fields must be positive, except weight, which may be 0. The maxWeight field is required and strictly enforced.
Sample BOXES.js
const BOXES = [
{ name: "15x15x15", length: 15, width: 15, height: 15, weight: 0, maxWeight: 150 },
{ name: "21x21x17", length: 21, width: 21, height: 17, weight: 0, maxWeight: 150 },
{ name: "32x22x16", length: 32, width: 22, height: 16, weight: 0, maxWeight: 150 },
{ name: "32x22x23", length: 32, width: 22, height: 23, weight: 0, maxWeight: 150 },
{ name: "44x33x16", length: 44, width: 33, height: 16, weight: 0, maxWeight: 150 },
{ name: "44x33x28", length: 44, width: 33, height: 28, weight: 0, maxWeight: 150 }
];
Pack Method
The pack method is responsible for selecting appropriate boxes and grouping items into parcels based on their dimensions, weight, quantity, and shipping type.
Method Signature
const packer = new SmartPacker({ boxes });
const result = packer.pack(items);
Arguments
items(Array): An array of item objects to be packed. Each item must include dimensions, weight, quantity, and an optionalshippingTypevalue that controls how the item is treated during packing.
Returns
The method returns an object containing the selected boxes and generated parcels, along with diagnostic arrays for rejected or unfitted inputs.
Items Input
Items are provided to the pack method as an array of objects:
{
name: "Item 1",
length: 10,
width: 5,
height: 3,
weight: 1,
quantity: 2,
shippingType: "rigid"
}
shippingType(String, Optional): One ofrigid,semi_rigid,flexible,soft,very_soft, ornestable.- If omitted,
shippingTypedefaults torigid. nestIncrement(Number, Optional): Used only fornestableitems.
Usage Example
import { enrichItemDetails, SmartPacker } from "./modules.js";
/**
* Define the list of available boxes SmartPacker can choose from.
* - length/width/height are INNER dimensions (must match the units used by your item dimensions).
* - weight is the empty box weight.
* - maxWeight is the maximum allowed total weight (box + packed items). This is enforced by SmartPacker.
*/
const BOXES = [
{ name: "15x15x15", length: 15, width: 15, height: 15, weight: 0, maxWeight: 150 },
{ name: "21x21x17", length: 21, width: 21, height: 17, weight: 0, maxWeight: 150 },
{ name: "32x22x16", length: 32, width: 22, height: 16, weight: 0, maxWeight: 150 },
{ name: "32x22x23", length: 32, width: 22, height: 23, weight: 0, maxWeight: 150 },
{ name: "44x33x16", length: 44, width: 33, height: 16, weight: 0, maxWeight: 150 },
{ name: "44x33x28", length: 44, width: 33, height: 28, weight: 0, maxWeight: 150 }
];
export async function calculateShippingRates(DATA, env) {
/**
* 1) Enrich items with additional Shopify data + metafields.
* Here we fetch up to 15 variant metafields from the "custom" namespace and attach them to:
* item.metafields.custom
* Assumption: your variant metafields include custom.length, custom.width, custom.height.
*/
await enrichItemDetails(DATA, [{ namespace: "custom", size: 15 }]);
/**
* 2) Transform JsRates/Shopify items into the simplified item schema SmartPacker expects.
* SmartPacker expects dimensions + weight per item and supports quantities.
*
* Important:
* - Keep dimension units consistent with BOXES (e.g., cm with cm).
* - Weight should be in the same unit used by your BOXES.maxWeight (e.g., kg with kg).
* - shippingType influences packing strategy. If you don't set it, SmartPacker treats items as "rigid" by default.
*/
const itemData = DATA.items.map((item, idx) => {
// Use SKU as a stable label if available; otherwise create a fallback name.
const name = item?.sku || `item_${idx + 1}`;
// Read dimensions from variant metafields; fall back to 10 if missing.
// (You can replace these defaults with your own validation/handling.)
const length = parseFloat(item?.metafields?.custom?.length || 10);
const width = parseFloat(item?.metafields?.custom?.width || 10);
const height = parseFloat(item?.metafields?.custom?.height || 10);
// Convert grams → kilograms, then round to 2 decimals (example approach).
// If item.grams is missing, we fall back to 100g (0.10kg) to avoid NaN.
const weightKg = Math.round(((item?.grams || 100) / 1000) * 100) / 100;
return {
shippingType: "rigid", // rigid | semi_rigid | flexible | soft | very_soft | nestable
name, // for debugging + output labeling
length, width, height, // item dimensions (match BOXES units)
weight: weightKg, // item weight (match maxWeight units)
quantity: item.quantity // SmartPacker will account for multiple units of the same item
};
});
/**
* 3) Create a SmartPacker instance.
* - boxes: required
* - fillPercentages: optional tuning; here we allow 90% utilization for rigid packing heuristics.
*/
const packer = new SmartPacker({
boxes: BOXES,
fillPercentages: { rigid: 0.9 }
});
/**
* 4) Run packing.
* result includes:
* - parcels: selected box dimensions + total parcel weight + box name
* - boxIndices: indices into BOXES array that were used
* - unfittedItems / invalidItems / invalidBoxes for troubleshooting
*/
const result = packer.pack(itemData);
/**
* 5) Use `result.parcels` to drive carrier rating calls or build your own shipping rules.
* Example ideas:
* - Rate each parcel individually and sum the costs.
* - If result.unfittedItems is not empty, return no rates or a fallback rate.
* - Store result as JSON in a metafield for debugging/analytics.
*/
// The rest of your code follows here
}
Output
The pack method returns an object with the following structure:
{
"boxIndices": [],
"parcels": [
{
"length": "number",
"width": "number",
"height": "number",
"weight": "number",
"name": "string"
}
],
"unfittedItems": [],
"invalidItems": [],
"invalidBoxes": [],
"error": "string"
}
Notes
boxIndicescontains indices into the inputboxesarray.parcelscontain the final parcel definitions;weightincludes box and item weights.unfittedItemslists items that could not be packed into any available box.invalidItemsandinvalidBoxescontain rejected inputs due to invalid dimensions or weight.erroris present only if an unexpected internal error occurred.
Behavior Notes
- Rigid items are packed using 3D bin packing.
- Semi-rigid and flexible items are scaled by the cube-root of the fill percentage, grouped by footprint, and stacked by height.
- Nestable items are grouped by footprint and combined using a base height plus
nestIncrementper additional item. - Soft and very soft items may fold once and compress by the defined ratio; both folded and original dimensions are checked before placement.
- Maximum box weight is enforced in all branches.
- If no box fits a flexible stack, those items are returned in
unfittedItems.
Limitations
- Soft item placement does not use full 3D bin packing.
- Weight distribution for soft-only cases is heuristic.
- Orientation handling for flexible items is limited.
- Very large item counts may impact performance due to bin duplication.
